/// <reference path="shelfpack.d.ts"/>

import { StyleImage } from "../style/StyleImage";
import { RGBAImage } from "../util/Image";
import { Texture } from "./Texture";
import { ImagePosition, imagePosition } from "./ImageAtlas";

import ShelfPack = require("@mapbox/shelf-pack");

// When copied into the atlas texture, image data is padded by one pixel on each side. Icon
// images are padded with fully transparent pixels, while pattern images are padded with a
// copy of the image data wrapped from the opposite side. In both cases, **this ensures the
// correct behavior of GL_LINEAR texture sampling mode.**
const padding = 1;

interface Pattern {
  bin: Bin;
  position: ImagePosition;
}

/*
  Builds a texture atlas for pattern images.
*/
export class ImageManager {
  images: { [key: string]: StyleImage };
  loaded: boolean;

  shelfPack: ShelfPack;
  patterns: { [key: string]: Pattern };
  atlasImage: RGBAImage;
  atlasTexture?: Texture;
  dirty: boolean;

  constructor() {
    this.images = {};
    this.loaded = false;

    this.shelfPack = new ShelfPack(64, 64, { autoResize: true });
    this.patterns = {};
    this.atlasImage = RGBAImage.create({ width: 64, height: 64 });
    this.dirty = true;
  }

  isLoaded() {
    return this.loaded;
  }

  setLoaded(loaded: boolean) {
    if (this.loaded === loaded) {
      return;
    }

    this.loaded = loaded;
  }

  getImage(id: string): StyleImage {
    return this.images[id];
  }

  addImage(id: string, image: StyleImage) {
    this.images[id] = image;
  }

  removeImage(id: string) {
    delete this.images[id];

    const pattern = this.patterns[id];
    if (pattern) {
      this.shelfPack.unref(pattern.bin);
      delete this.patterns[id];
    }
  }

  // Pattern stuff

  getPixelSize() {
    return {
      width: this.shelfPack.w,
      height: this.shelfPack.h
    };
  }

  getPattern(id: string): ImagePosition | null {
    const pattern = this.patterns[id];
    if (pattern) {
      return pattern.position;
    }

    const image = this.getImage(id);
    if (!image) {
      return null;
    }

    const width = image.data.width + padding * 2;
    const height = image.data.height + padding * 2;

    const bin = this.shelfPack.packOne(width, height);
    if (!bin) {
      return null;
    }

    RGBAImage.resize(this.atlasImage, this.getPixelSize());

    // 将新图片的内容拷贝到atlasImage
    const src = image.data;
    const dst = this.atlasImage;

    const x = bin.x + padding;
    const y = bin.y + padding;
    const w = src.width;
    const h = src.height;

    RGBAImage.copy(src, dst, { x: 0, y: 0 }, { x, y }, { width: w, height: h });

    // Add 1 pixel wrapped padding on each side of the image.
    RGBAImage.copy(
      src,
      dst,
      { x: 0, y: h - 1 },
      { x: x, y: y - 1 },
      { width: w, height: 1 }
    ); // TOP
    RGBAImage.copy(
      src,
      dst,
      { x: 0, y: 0 },
      { x: x, y: y + h },
      { width: w, height: 1 }
    ); // BOTTOM
    RGBAImage.copy(
      src,
      dst,
      { x: w - 1, y: 0 },
      { x: x - 1, y: y },
      { width: 1, height: h }
    ); // LEFT
    RGBAImage.copy(
      src,
      dst,
      { x: 0, y: 0 },
      { x: x + w, y: y },
      { width: 1, height: h }
    ); // RIGHT

    this.dirty = true;

    const position = imagePosition(bin, image);
    this.patterns[id] = { bin, position };
    return position;
  }

  bind(gl: WebGLRenderingContext) {
    if (!this.atlasTexture || this.dirty) {
      this.atlasTexture = new Texture(gl, this.atlasImage, gl.RGBA);
      this.dirty = false;
    }
    // else if (this.dirty) {
    //   this.atlasTexture.update(this.atlasImage);
    //   this.dirty = false;
    // }

    this.atlasTexture.bind(gl.LINEAR, gl.CLAMP_TO_EDGE);
  }
}
